//
// Created by 郝羽 on 2017/11/17.
//

#ifndef RTC_XSWITCH2_TRANSPORT_FEEDBACK_HPP
#define RTC_XSWITCH2_TRANSPORT_FEEDBACK_HPP

#include <iostream>
#include <iomanip>
#include <functional>

#include "xcutil.h"
#include "xm_log.h"

class TransportFeedback {
    enum class Symbol {
        NotReceived,
        ReceivedSmallDelta,
        ReceivedLargeDelta,
        Reserved,
    };
    class Delta {
    public:
        uint16_t seq;
        int32_t delta;
    };

    static constexpr int kDeltaUnitUs = 250;
public:
    uint32_t ssrc_sender;
    uint32_t ssrc_media;
    uint16_t base_seq;
    uint64_t base_time;
    uint8_t  fb_seq;

    std::vector<Delta> deltas;
public:
//    TransportFeedback() {};
//    ~TransportFeedback() {};

//    https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.5
//
//     0                   1                   2                   3
//     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    |V=2|P|  FMT=15 |    PT=205     |           length              |
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    |                     SSRC of packet sender                     |
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    |                      SSRC of media source                     |
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    |      base sequence number     |      packet status count      |
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    |                 reference time                | fb pkt. count |
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    |          packet chunk         |         packet chunk          |
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    .                                                               .
//    .                                                               .
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    |         packet chunk          |  recv delta   |  recv delta   |
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    .                                                               .
//    .                                                               .
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//    |           recv delta          |  recv delta   | zero padding  |
//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


    void print_hex(const char* prefix, const uint8_t* buf, size_t len) {
        std::cout << prefix << ": ";
        
        for (size_t i = 0; i < len; ++i) {
            std::cout << std::setw(2) << std::hex << std::setfill('0') << int(buf[i]) << std::dec << " ";
        }
        std::cout << std::endl;
    }

    bool parse(const uint8_t* buf, size_t len) {
        reset();
//        print_hex("transport-feedback", buf, len);
        if (len < 4) {
            return false;
        }
        size_t fb_len = be_get_u16(buf+2) * 4 + 4;
        if (len < fb_len) {
            return false;
        }
        ssrc_sender = be_get_u32(buf+4);
        ssrc_media = be_get_u32(buf+8);
        base_seq = be_get_u16(buf+12);
        uint16_t num_packets = be_get_u16(buf+14);
//        reference time:  24 bits Signed integer indicating an absolute
//        reference time in some (unknown) time base chosen by the
//        sender of the feedback packets.  The value is to be
//        interpreted in multiples of 64ms.  The first recv delta
//        in this packet is relative to the reference time.  The
//        reference time makes it possible to calculate the delta
//        between feedbacks even if some feedback packets are lost,
//        since it always uses the same time base.
        base_time = be_get_u24(buf+16);
        base_time *= 64;
//        // debug
//        std:: cout << "base time: " << base_time << " ms" << std::endl;
//        // debug

        fb_seq = buf[19];

        size_t pos = 20;
        size_t end = fb_len;

        //print_hex("transport-feedback-symbols", buf+pos, len-pos);
        std::vector<Symbol> symbols;
        while(symbols.size() < num_packets) {
            if (pos + 2 > end) {
                return false;
            }
            if (!_parse_chunk(symbols, buf+pos, num_packets - symbols.size())) {
                return false;
            }
            pos += 2;
        }
        // size_t delta_begin = pos;

        //debug

        //std::cout << "symbols: ";
//        for (auto s : symbols) {
//            switch(s) {
//                case Symbol::ReceivedSmallDelta: std::cout << " S"; break;
//                case Symbol ::ReceivedLargeDelta: std::cout << " L"; break;
//                case Symbol::NotReceived: std::cout << " N"; break;
//                case Symbol::Reserved: std::cout << " R"; break;
//                default: std::cout << " X ";
//            }
//        }
        //std::cout << std::endl;
        //debug
//        print_hex("transport-feedback-deltas", buf+pos, len-pos);
        uint16_t seq = base_seq;
        for (auto s : symbols) {
            int32_t delta = 0;
            switch (s) {
                case Symbol::ReceivedSmallDelta: {
                    delta = buf[pos];
                    pos += 1;
                } break;
                case Symbol::ReceivedLargeDelta: {
                    delta = (int16_t)be_get_u16(buf + pos);
//                    if (delta < -10000) {
//                        std::cout << "check it" << std::endl;
//                    }
                    pos += 2;
                } break;
                default:
                    seq++;
                    continue;
            }
            deltas.emplace_back(Delta{seq++, delta * kDeltaUnitUs / 1000});
        }
        //debug
//        std::cout << "deltas: ";
//        for (auto d : deltas) {
//            std::cout << d.seq <<":"<< d.delta << " ";
//        }
//        std::cout << std::endl;
        //debug
        return true;
    };

    void reset() {
        ssrc_sender = 0;
        ssrc_media = 0;
        base_seq = 0;
        base_time = 0;
        fb_seq = 0;
        deltas.clear();
    };

    void for_each(std::function<void(uint16_t, int32_t)> cb) const {
        for (auto d : deltas) {
            cb(d.seq, d.delta);
        }
    };
private:
    bool _parse_chunk(std::vector<Symbol>& symbols, const uint8_t* buf, size_t max) {
        uint8_t byte = buf[0] & 0xC0;
        switch (byte) {
        case 0xC0:
            return _parse_chunk_two_bit_vector(symbols, buf);
        case 0x80:
            return _parse_chunk_one_bit_vector(symbols, buf);
        }
        return _parse_chunk_run_length(symbols, buf, max);
    };

    bool _parse_chunk_two_bit_vector(std::vector<Symbol>& symbols, const uint8_t* buf) {
        //std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << (int)buf[0] << " " << std::setw(2) << std::setfill('0') << (int)buf[1] << " " << std::dec;
        symbols.emplace_back(_decode_symbol((buf[0] >> 4) & 0x03));
        symbols.emplace_back(_decode_symbol((buf[0] >> 2) & 0x03));
        symbols.emplace_back(_decode_symbol((buf[0] >> 0) & 0x03));
        symbols.emplace_back(_decode_symbol((buf[1] >> 6) & 0x03));
        symbols.emplace_back(_decode_symbol((buf[1] >> 4) & 0x03));
        symbols.emplace_back(_decode_symbol((buf[1] >> 2) & 0x03));
        symbols.emplace_back(_decode_symbol((buf[1] >> 0) & 0x03));
        //std::cout << std::endl;
        return true;
    };

    bool _parse_chunk_one_bit_vector(std::vector<Symbol>& symbols, const uint8_t* buf) {
        //std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << (int)buf[0] << " " << std::dec;
        symbols.emplace_back(_decode_symbol((buf[0] >> 5) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[0] >> 4) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[0] >> 3) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[0] >> 2) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[0] >> 1) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[0] >> 0) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[1] >> 7) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[1] >> 6) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[1] >> 5) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[1] >> 4) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[1] >> 3) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[1] >> 2) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[1] >> 1) & 0x01));
        symbols.emplace_back(_decode_symbol((buf[1] >> 0) & 0x01));
        //std::cout << std::endl;
        return true;
    };

    bool _parse_chunk_run_length(std::vector<Symbol>& symbols, const uint8_t* buf, size_t max) {
        //std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << (int)buf[0] << " " << std::dec;
        Symbol s = _decode_symbol((buf[0] >> 5) & 0x03);
        uint16_t count = (static_cast<uint16_t>(buf[0] & 0x1F) << 8) | buf[1];
        if (count > max) {
            return false;
        }
        //std::cout << "repeat " << count << " times" << std::endl;
        while(count--) {
            symbols.emplace_back(s);
        }
        return true;
    };

    Symbol _decode_symbol(uint8_t value) {
        switch(value) {
            case 0:
                //std::cout << "N ";
                return Symbol::NotReceived;
            case 1:
                //std::cout << "S ";
                return Symbol::ReceivedSmallDelta;
            case 2:
                //std::cout << "L ";
                return Symbol::ReceivedLargeDelta;
        }
        //std::cout << "X ";
        return Symbol::Reserved;
    };
};

#endif //RTC_XSWITCH2_TRANSPORT_FEEDBACK_HPP
